package emby.lang;

import emby.err.SyntaxError;
import emby.err.TypeError;
import emby.overload.OverloadDiv;
import emby.overload.OverloadMinus;
import emby.overload.OverloadMod;
import emby.overload.OverloadMul;
import emby.overload.OverloadPlus;

public class BuiltInOperators {
	public final static NativeFunction add = new NativeFunction("add") {
		private static final long serialVersionUID = -8510859030640014132L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args == null || args.length != 2) {
				throw new SyntaxError(line, "Addition takes only two operands");
			}
			if (args[0] instanceof OverloadPlus) { // if operator is overloaded
				return ((OverloadPlus) args[0]).operatorPlus(line, scope, args[1]);
			} else {// perform numerical addition
				if ((args[0] instanceof String) || (args[1] instanceof String))
					return args[0].toString() + args[1].toString();
				if ((args[0] instanceof Integer)
						&& (args[1] instanceof Integer))
					return ((Number) args[0]).intValue()
							+ ((Number) args[1]).intValue();
				else if ((args[0] instanceof Number)
						&& (args[1] instanceof Number))
					return ((Number) args[0]).doubleValue()
							+ ((Number) args[1]).doubleValue();
				else {
					throw new TypeError(line, args[0] + "("
							+ args[0].getClass() + ")"
							+ " cannot be added with " + "("
							+ args[1].getClass() + ")");
				}
			}
		}

		public String toString() {
			return "+";
		}
	};
	public final static NativeFunction sub = new NativeFunction("sub") {
		private static final long serialVersionUID = -3839175173387954803L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line,
						"Subtraction takes only two operands");
			}
			if (args[0] instanceof OverloadMinus) { // if operator is
				return ((OverloadMinus) args[0]).operatorMinus(line, scope, args[1]);
			} else {// perform numerical addition
				if ((args[0] instanceof Integer)
						&& (args[1] instanceof Integer))
					return ((Number) args[0]).intValue()
							- ((Number) args[1]).intValue();
				else if ((args[0] instanceof Number)
						&& (args[1] instanceof Number))
					return ((Number) args[0]).doubleValue()
							- ((Number) args[1]).doubleValue();
				else {
					throw new TypeError(line, args[0] + "("
							+ args[0].getClass() + ")"
							+ " cannot be subtracted with " + "("
							+ args[1].getClass() + ")");
					// return null;
				}
			}
		}
		public String toString() {
			return "-";
		}
	};

	public final static NativeFunction mul = new NativeFunction("mul") {
		private static final long serialVersionUID = -7694397195324471632L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line,
						"Multiplication takes only two operands");
			}
			if (args[0] instanceof OverloadMul) { // if operator is
			 return ((OverloadMul) args[0]).operatorMul(line, scope, args[1]);
			 } else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue()
						* ((Number) args[1]).intValue();
			else if ((args[0] instanceof Number) && (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue()
						* ((Number) args[1]).doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be multiplied with " + "("
						+ args[1].getClass() + ")");
				// return null;
			}
		}}

		public String toString() {
			return "*";
		}
	};
	public final static NativeFunction div = new NativeFunction("div") {
		private static final long serialVersionUID = -6983869334920056009L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "Division takes only two operands");
			}
			if (args[0] instanceof OverloadDiv) { // if operator is
			return ((OverloadDiv) args[0]).operatorDiv(line, scope, args[1]);
			 } else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue()
						/ ((Number) args[1]).intValue();
			else if ((args[0] instanceof Number) || (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue()
						/ ((Number) args[1]).doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be divided with " + "("
						+ args[1].getClass() + ")");
				// return null;
			}
		}}

		public String toString() {
			return "/";
		}
	};
	public final static NativeFunction mod = new NativeFunction("mod") {
		private static final long serialVersionUID = 5108240746554380457L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "Modulus takes only two operands");
			}
			if (args[0] instanceof OverloadMod) { // if operator is
			return ((OverloadMod) args[0]).operatorMod(line, scope, args[1]);
			} else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue()
						% ((Number) args[1]).intValue();
			else if ((args[0] instanceof Number) || (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue()
						% ((Number) args[1]).doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be % with " + "(" + args[1].getClass()
						+ ")");
				// return null;
			}
		}}

		public String toString() {
			return "%";
		}
	};
	public final static NativeFunction eqeq = new NativeFunction("eqeq") {
		private static final long serialVersionUID = -5725874434706701129L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "== takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition

			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() == ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) && (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() == ((Number) args[1])
						.doubleValue();
			else if ((args[0] instanceof Boolean)
					&& (args[1] instanceof Boolean))
				return ((Boolean) args[0]) == ((Boolean) args[1]);
			else {
				// throw new TypeError(line, args[0]+"("+args[0].getClass()+")"
				// + " cannot be equated with " +"("+args[1].getClass()+")");
				return args[0].equals(args[1]);
			}

		}

		public String toString() {
			return "==";
		}
	};
	public final static NativeFunction noteq = new NativeFunction("noteq") {
		private static final long serialVersionUID = 4885544139323043562L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "!= takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition

			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() != ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) && (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() != ((Number) args[1])
						.doubleValue();
			else if ((args[0] instanceof Boolean)
					&& (args[1] instanceof Boolean))
				return ((Boolean) args[0]) != ((Boolean) args[1]);
			else {
				return !args[0].equals(args[1]);
				// throw new TypeError(line, args[0]+"("+args[0].getClass()+")"
				// + " cannot be != with " +"("+args[1].getClass()+")");
				// return null;
			}

		}

		public String toString() {
			return "!=";
		}
	};

	public final static NativeFunction greater = new NativeFunction("greater") {
		private static final long serialVersionUID = -8347193765028089520L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "greater takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() > ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) || (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() > ((Number) args[1])
						.doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be > with " + "(" + args[1].getClass()
						+ ")");
				// return null;
			}
		}

		public String toString() {
			return ">";
		}
	};

	public final static NativeFunction less = new NativeFunction("less") {
		private static final long serialVersionUID = 4647938001493826897L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "less takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() < ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) || (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() < ((Number) args[1])
						.doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be < with " + "(" + args[1].getClass()
						+ ")");
				// return null;
			}
		}

		public String toString() {
			return "<";
		}
	};

	public final static NativeFunction greatereq = new NativeFunction("greatereq") {
		private static final long serialVersionUID = 6652195341994345037L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "greatereq takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() >= ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) || (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() >= ((Number) args[1])
						.doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be >= with " + "("
						+ args[1].getClass() + ")");
				// return null;
			}
		}

		public String toString() {
			return ">=";
		}
	};
	public final static NativeFunction lesseq = new NativeFunction("lesseq") {
		private static final long serialVersionUID = 6149099738440785613L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "lesseq takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() <= ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) || (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() <= ((Number) args[1])
						.doubleValue();
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be <= with " + "("
						+ args[1].getClass() + ")");
				// return null;
			}
		}

		public String toString() {
			return "<=";
		}
	};
	public final static NativeFunction and = new NativeFunction("and") {
		private static final long serialVersionUID = -3158979614531897489L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "AND takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Boolean) && (args[1] instanceof Boolean))
				return ((Boolean) args[0]) && ((Boolean) args[1]);
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be anded with " + "("
						+ args[1].getClass() + ")");
				// return null;
			}
		}

		public String toString() {
			return "AND";
		}
	};
	public final static NativeFunction or = new NativeFunction("or") {
		private static final long serialVersionUID = 6429724504358650360L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "OR takes only two operands");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Boolean) && (args[1] instanceof Boolean))
				return ((Boolean) args[0]) || ((Boolean) args[1]);
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be or'd with " + "("
						+ args[1].getClass() + ")");
				// return null;
			}
		}

		public String toString() {
			return "OR";
		}
	};
	public final static NativeFunction not = new NativeFunction("not") {
		private static final long serialVersionUID = 8585694477400312138L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 1) {
				throw new SyntaxError(line, "NOT takes only one operand");
			}
			// if (args[0] instanceof IOverloadDiv) { // if operator is
			// overloaded
			// return ((IOverloadDiv) args[0]).operator_div(args[1]);
			// } else {// perform numerical addition
			if ((args[0] instanceof Boolean))
				return !((Boolean) args[0]);
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be !");
				// return null;
			}
		}

		public String toString() {
			return "NOT";
		}
	};

	public final static NativeFunction is = new NativeFunction("is") {
		private static final long serialVersionUID = -2572743750707433847L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line, "is takes only two operands");
			}

			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return ((Number) args[0]).intValue() == ((Number) args[1])
						.intValue();
			else if ((args[0] instanceof Number) && (args[1] instanceof Number))
				return ((Number) args[0]).doubleValue() == ((Number) args[1])
						.doubleValue();
			else if ((args[0] instanceof Boolean)
					&& (args[1] instanceof Boolean))
				return ((Boolean) args[0]) == ((Boolean) args[1]);
			else {
				return args[0] == args[1];
			}
		}

		public String toString() {
			return "IS";
		}
	};

	public final static NativeFunction carrot = new NativeFunction("carrot") {
		private static final long serialVersionUID = -2572743750707433847L;

		public Object execute(int line, Scope scope, Object... args) {
			if (args.length != 2) {
				throw new SyntaxError(line,
						"Exponent/XOR takes only two operands");
			}

			if ((args[0] instanceof Integer) && (args[1] instanceof Integer))
				return (int) Math.pow((Integer) args[0], (Integer) args[1]);
			else if ((args[0] instanceof Number) && (args[1] instanceof Number))
				return Math.pow(((Number) args[0]).doubleValue(),
						((Number) args[1]).doubleValue());
			else if ((args[0] instanceof Boolean)
					&& (args[1] instanceof Boolean))
				return ((Boolean) args[0]) ^ ((Boolean) args[1]);
			else {
				throw new TypeError(line, args[0] + "(" + args[0].getClass()
						+ ")" + " cannot be ^ with " + "(" + args[1].getClass()
						+ ")");
			}
		}

		public String toString() {
			return "^";
		}
	};

}
